iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
Kubernetes

異世界生存戰記:30天煉成GKE大師系列 第 25

Day25 GPU 硬體層級切割魔法 Multi-Instance GPUs(MIG)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241009/201690177iIh7sq8QA.jpg

前言

昨天介紹了如何在 GKE 中使用 GPU 節點部署 Mixtral-8x7B-Instruct-v0.1 模型。然而讀者有沒有發現問題,部署過程中使用需要較小記憶體的 Model(mistralai/Mistral-Nemo-Instruct-2407) 會遇到資源浪費。

以 H100 GPU 為例,單卡就擁有 80GB 的記憶體,即使模型只需要一小部分資源,也佔用了一整張卡,造成效能閒置及成本增加。

本章將探討如何解決這個問題,提升 GPU 使用率,並有效降低成本。

Multi-Instance GPUs(MIG) 介紹

為了提高 GPU 利用率,Multi-Instance GPU 可讓您將單個受支持的 GPU 進行分區,最多可分為七個切片。每個切片可以獨立分配給節點上的一個容器,每個 GPU 最多支持 7 個容器。Multi-Instance GPU 在工作負載之間提供硬件隔離,並為 GPU 上運行的所有容器提供一致且可預測的 QoS。

對於 CUDA 應用,每個 GPU 分區都顯示為常規 GPU 資源。這提供了硬體級的隔離,確保每個實例的效能不受其他實例的影響,提供 QoS (服務品質) 保證。

如需詳細了解 Multi-Instance GPU,請參閱 NVIDIA Multi-Instance GPU 用戶指南

但是目前支持的 Multi-Instance GPU 只有三種,NVIDIA A100 (40GB),NVIDIA A100 (80GB),NVIDIA H100 (80GB)

Multi-Instance GPU 分區

A100 GPU 和 H100 GPU 由 7 個計算單元和 8 個記憶體單元組成,可以劃分為不同大小的 GPU 實例。

GPU 分區大小使用以下語法:[compute]g.[memory]gb。例如,1g.5gb 的 GPU 分區大小是指具有一個計算單元(GPU 上流式多處理器的 1/7)和一個記憶體單元 (5GB) 的 GPU 實例。可以在部署 Autopilot 工作負載或創建標準集群時指定 GPU 的分區大小。

下表列出了 GKE 支持的分區大小:

分區大小 GPU 實例
GPU:NVIDIA A100 (40GB) (nvidia-tesla-a100)
1g.5gb 7
2g.10gb 3
3g.20gb 2
7g.40gb 1
GPU:NVIDIA A100 (80GB) nvidia-a100-80gb
1g.10gb 7
2g.20gb 3
3g.40gb 2
7g.80gb 1
GPU:NVIDIA H100 (80GB) (nvidia-h100-80gb)
1g.10gb 7
1g.20gb 4
2g.20gb 3
3g.40gb 2
7g.80gb 1

使用 Terraform 建立 H100(3g.40gb) 的 GPU MIG Node Pool

為了部署實驗環境所需的機器,可以參考 Day3 的 Terraform 範例,使用 Day3 範例創建的Cluster。

node-pool-variables.tf

### node-pool-variables.tf
module "gke" {
  node_pools = [
    var.h100-partition-3g.config,
  ]

  node_pools_labels = {
    "${var.h100-partition-3g.config.name}" = var.h100-partition-3g.kubernetes_label
  }

  node_pools_taints = {
	  "${var.h100-partition-3g.config.name}" = var.h100-partition-3g.taints
  }

  node_pools_resource_labels = {
    "${var.h100-partition-3g.config.name}" = var.h100-partition-3g.node_pools_resource_labels
  }
}
### Node pool
variable "h100-partition-3g" {
  default = {
    config = {
      name               = "h100-partition-3g"
      machine_type       = "a3-highgpu-8g"
      accelerator_type   = "nvidia-h100-80gb"
      accelerator_count  = "8"
# H100 avaliable values 1g.10gb, 1g.20gb, 2g.20gb, 3g.40gb, 7g.80gb
      gpu_partition_size = "3g.40gb"
      gpu_driver_version = "LATEST"
      node_locations     = "us-central1-c"
      max_pods_per_node  = 64
      autoscaling        = false
      node_count         = 1
      local_ssd_count    = 0
      disk_size_gb       = 2000
      local_ssd_ephemeral_storage_count = 16
      spot               = true
      disk_type          = "pd-ssd"
      image_type         = "COS_CONTAINERD"
      enable_gcfs        = false
      enable_gvnic       = false
      logging_variant    = "DEFAULT"
      auto_repair        = true
      auto_upgrade       = true
      preemptible        = false
    }
    kubernetes_label = {
      role = "h100-standard"
    }
    taints = [
          key    = "nvidia/share"
          value  = "nvidia-mig"
          effect = "NO_SCHEDULE"
    ]
  }
}

使用以下指令查看 Node 的標籤,可以看到會自動被打上 cloud.google.com/gke-gpu-partition-size: 3g.40gb 的標籤,可以使用此標籤來作為 Pod 的 nodeAffinity。

$ kubectl get nodes $h100_partition_3g_Node_Name --show-labels

 # ...以上省略...
 cloud.google.com/gke-gpu-partition-size: 3g.40gb

使用上篇文章部署的 mistralai/Mistral-Nemo-Instruct-2407 來示範,這個模型大約 24.5 GB,所以我們切分為 3g.40gb,40 GB 的記憶體夠 Mistral-Nemo 這個模型使用。預計一台 a3-highgpu-8g 總共可以部署 16 個 Pods,replicas 定為 16。

Deployment.yaml

# llm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-deployment
  namespace: ai
spec:
  replicas: 16
  selector:
    matchLabels:
      app: llm
  template:
    metadata:
      labels:
        app: llm
    spec:
      terminationGracePeriodSeconds: 60
      volumes:
        - name: dshm
          emptyDir:
            sizeLimit: "10Gi"
            medium: "Memory"
      containers:
      - name: llm-chat
        image: vllm/vllm-openai:v0.6.2
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - mountPath: /dev/shm
            name: dshm
        env:
          - name: HUGGING_FACE_HUB_TOKEN
        # 改成自己的 HUGGING_FACE_HUB_TOKEN
            value: "$HUGGING_FACE_HUB_TOKEN"
        # 隨便產生一組 VLLM_API_KEY,待會 Call 這隻服務會使用到
          - name: VLLM_API_KEY
            value: "$VLLM_API_KEY"
        args:
        - --host=0.0.0.0
        - --port=8000
        - --model=mistralai/Mistral-Nemo-Instruct-2407
        - --tensor-parallel-size=1
        - --swap-space=16
        - --dtype=bfloat16
        - --gpu-memory-utilization=0.9
        - --max-model-len=8192
        - --max-num-seqs=256
        ports:
        - name: http
          containerPort: 8000
        readinessProbe:
          httpGet:
            path: /health
            port: http
          periodSeconds: 20
        livenessProbe:
          httpGet:
            path: /health
            port: http
          periodSeconds: 20
        startupProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 300 # LLM 啟動較久,這裡設定為 300 秒
          periodSeconds: 30 # 最多可以等到 1 小時(30 * 120 = 3600s)
          failureThreshold: 120
        resources:
          requests:
            cpu: "10"
            memory: 30Gi
            nvidia.com/gpu: "1"
          limits: 
            cpu: "24"
            memory: 100Gi
            nvidia.com/gpu: "1"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: cloud.google.com/gke-gpu-partition-size
                    operator: In
                    values:
                      - 3g.40gb
      tolerations:
      - key: "nvidia.com/gpu"
        operator: Equal
        value: "present"
        effect: NoSchedule
      - key: "nvidia/share"
        operator: Equal
        value: "nvidia-mig"
        effect: NoSchedule

以上 Yaml 有以下幾點需要注意:

  • containers.args.tensor-parallel-size=1 :雖然我們有使用 MIG ,但是每個 GPU 分區都顯示為常規 GPU 資源,所以當作單張卡設定為 1。
  • resources.requests.nvidia.com/gpu=1:同上原因,雖然我們有使用 MIG ,但是每個 GPU 分區都顯示為常規 GPU 資源,所以當作單張卡設定為 1。
  • tolerations記得要加入我們在 Node Pool 打上的 Taints 以及 GKE GPU Node 預設的 Taints。

將 Deployment 部署上去後,等待大約 1 小時,可以看到 16 個 LLM 的 Pod 都 Ready 了。

$ kubectl get pods -n ai
NAME                             READY   STATUS    RESTARTS   AGE
llm-deployment-b6bcdb6c4-2pcvw   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-8h68d   1/1     Running   0          55m
llm-deployment-b6bcdb6c4-c8rph   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-ctj8n   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-cxrv9   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-fmnwc   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-g4g4w   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-hxvrl   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-jt64k   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-kwqf6   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-m94tt   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-mmbhk   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-tjgps   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-vpd96   1/1     Running   0          1h10m
llm-deployment-b6bcdb6c4-wprqk   1/1     Running   0          54m
llm-deployment-b6bcdb6c4-znvvr   1/1     Running   0          54m

可以任意進入其中一個 Pod ,輸入指令 nvidia-smi 來查看當前 Pod 所使用的 GPU 的狀態。

# 輸入指令進入 Pod
$ exec kubectl exec -i -t -n ai $Pod_Name -c llm-chat -- sh -c "clear; (bash || ash || sh)"
# 進入 Pod 以後使用 nvidia-smi 查看當前 Pod 所在的 GPU 狀態,以 watch 每一秒刷新一次
root@$Pod_Name:/vllm-workspace$ watch -n 1 nvidia-smi

https://ithelp.ithome.com.tw/upload/images/20241009/20169017nwZ6tRLHFV.png

從輸出的格式中可以看到,使用了 NVIDIA H100 80GB GPU 正在運行,驅動版本為 550.90.07,CUDA 版本為 12.4。

  • 溫度和功耗: GPU 溫度為 34 攝氏度,功耗為 117 瓦,低於其 700 瓦的功耗上限。
  • MIG 配置: 你的 GPU 已啟用 MIG,並創建了一個 MIG 設備 (GI 1, CI 0, MIG Device 0)。這個 MIG 設備擁有 60 個 SM (Streaming Multiprocessors) 和 40GB 顯存。
  • 顯存使用: 總顯存為 80GB。由於啟用了 MIG (Multi-Instance GPU),一部分顯存被分配給了 MIG 設備,MIG 設備 0 占用了大約 37GB 顯存。

進入 GKE 的觀測能力頁面,調整到 DCGM,可以發現將顯示卡切半(3g.40g) 以後,可以使用到所有的 GPU 中的記憶體,使用率達到 91%,有效提升 GPU 使用率。

https://ithelp.ithome.com.tw/upload/images/20241009/20169017jNaJ7mllr6.png

接下來使用上一篇文章的 K6 壓測工具,會發現因為顯示卡切半,Pod 的數量變成兩倍,所以 Prompt Token Output 也會變成兩倍,提升 GPU 使用率的同時,也有效降低成本。

以下是我使用 Grafana 搭配 Prometheus dcgm exporter 所製成的監控圖表,和 GKE 的監控圖表看到的類似。但是也因為將 MIG 所以 GPU Utilization 及 GPU Mem Cpy Utilization 會看不到。

這裡有一個小亮點,當一台 8 張卡的 H100 高負載運轉時,耗電量可以達到 8 Kw 以上,根本吃電怪獸,筆者第一次壓測時也嚇到,這才了解到為什麼發展 AI 的同時也要蓋電廠。

https://ithelp.ithome.com.tw/upload/images/20241009/20169017UBqyFOWiJe.png

總結

本文示範了如何在 Google Kubernetes Engine (GKE) 上部署 Multi-Instance GPUs (MIG) 節點,並利用其高效能特性部署大型語言模型 (LLM)。

透過將 H100 GPU 切分為更小的 3g.40g 執行個體,我們得以更精細地分配 GPU 資源,滿足不同 LLM 推論任務的需求。 實驗中,我們成功將 Pod 數量提升兩倍,有效降低了單個請求的 GPU 成本,並提升了資源利用率。

此方法讓使用者能更彈性地運用昂貴的 GPU 資源,在控制成本的同時,維持 LLM 推論效能,對於追求高效能且經濟實惠的 AI 應用部署至關重要。 未來,MIG 技術將在 AI 領域扮演更重要的角色,尤其在日益增長的 LLM 推論需求下,其精細的資源分配能力將成為重要的成本優化策略。

參考文件


上一篇
Day24 GKE異世界:LLM 的 GPU 召喚儀式 x DCGM Exporter
下一篇
Day26 GPU 軟體層級切割魔法 Time-Slicing & MPS
系列文
異世界生存戰記:30天煉成GKE大師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言